Passed
Branch riff-file (a0264a)
by Rafael S.
03:11
created

WaveFile.getDwChannelMask_   B

Complexity

Conditions 6

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
dl 0
loc 21
rs 8.6666
c 0
b 0
f 0

1 Function

Rating   Name   Duplication   Size   Complexity  
A WaveFile.validateWavHeader_ 0 5 1
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFile class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import bitDepthLib from 'bitdepth';
33
import * as imaadpcm from 'imaadpcm';
34
import * as alawmulaw from 'alawmulaw';
35
import {encode, decode} from 'base64-arraybuffer-es6';
36
import RIFFFile from './lib/riff-file';
37
import writeString from './lib/write-string';
38
import dwChannelMask from './lib/dw-channel-mask';
39
import {unpackArray, packArrayTo, unpackArrayTo,
40
  unpack, packTo, packStringTo, packString, pack} from 'byte-data';
41
42
/**
43
 * A class to read, write and process wav files.
44
 */
45
export default class WaveFile extends RIFFFile {
46
47
  /**
48
   * @param {?Uint8Array=} wavBuffer A wave file buffer.
49
   * @throws {Error} If no 'RIFF' chunk is found.
50
   * @throws {Error} If no 'fmt ' chunk is found.
51
   * @throws {Error} If no 'data' chunk is found.
52
   */
53
  constructor(wavBuffer=null) {
54
    super();
55
    /**
56
     * Audio formats.
57
     * Formats not listed here should be set to 65534,
58
     * the code for WAVE_FORMAT_EXTENSIBLE
59
     * @enum {number}
60
     * @private
61
     */
62
    this.WAV_AUDIO_FORMATS = {
63
      '4': 17,
64
      '8': 1,
65
      '8a': 6,
66
      '8m': 7,
67
      '16': 1,
68
      '24': 1,
69
      '32': 1,
70
      '32f': 3,
71
      '64': 3
72
    };
73
    /**
74
     * The data of the 'fmt' chunk.
75
     * @type {!Object<string, *>}
76
     */
77
    this.fmt = {
78
      /** @type {string} */
79
      chunkId: '',
80
      /** @type {number} */
81
      chunkSize: 0,
82
      /** @type {number} */
83
      audioFormat: 0,
84
      /** @type {number} */
85
      numChannels: 0,
86
      /** @type {number} */
87
      sampleRate: 0,
88
      /** @type {number} */
89
      byteRate: 0,
90
      /** @type {number} */
91
      blockAlign: 0,
92
      /** @type {number} */
93
      bitsPerSample: 0,
94
      /** @type {number} */
95
      cbSize: 0,
96
      /** @type {number} */
97
      validBitsPerSample: 0,
98
      /** @type {number} */
99
      dwChannelMask: 0,
100
      /**
101
       * 4 32-bit values representing a 128-bit ID
102
       * @type {!Array<number>}
103
       */
104
      subformat: []
105
    };
106
    /**
107
     * The data of the 'fact' chunk.
108
     * @type {!Object<string, *>}
109
     */
110
    this.fact = {
111
      /** @type {string} */
112
      chunkId: '',
113
      /** @type {number} */
114
      chunkSize: 0,
115
      /** @type {number} */
116
      dwSampleLength: 0
117
    };
118
    /**
119
     * The data of the 'cue ' chunk.
120
     * @type {!Object<string, *>}
121
     */
122
    this.cue = {
123
      /** @type {string} */
124
      chunkId: '',
125
      /** @type {number} */
126
      chunkSize: 0,
127
      /** @type {number} */
128
      dwCuePoints: 0,
129
      /** @type {!Array<!Object>} */
130
      points: [],
131
    };
132
    /**
133
     * The data of the 'smpl' chunk.
134
     * @type {!Object<string, *>}
135
     */
136
    this.smpl = {
137
      /** @type {string} */
138
      chunkId: '',
139
      /** @type {number} */
140
      chunkSize: 0,
141
      /** @type {number} */
142
      dwManufacturer: 0,
143
      /** @type {number} */
144
      dwProduct: 0,
145
      /** @type {number} */
146
      dwSamplePeriod: 0,
147
      /** @type {number} */
148
      dwMIDIUnityNote: 0,
149
      /** @type {number} */
150
      dwMIDIPitchFraction: 0,
151
      /** @type {number} */
152
      dwSMPTEFormat: 0,
153
      /** @type {number} */
154
      dwSMPTEOffset: 0,
155
      /** @type {number} */
156
      dwNumSampleLoops: 0,
157
      /** @type {number} */
158
      dwSamplerData: 0,
159
      /** @type {!Array<!Object>} */
160
      loops: []
161
    };
162
    /**
163
     * The data of the 'bext' chunk.
164
     * @type {!Object<string, *>}
165
     */
166
    this.bext = {
167
      /** @type {string} */
168
      chunkId: '',
169
      /** @type {number} */
170
      chunkSize: 0,
171
      /** @type {string} */
172
      description: '', //256
173
      /** @type {string} */
174
      originator: '', //32
175
      /** @type {string} */
176
      originatorReference: '', //32
177
      /** @type {string} */
178
      originationDate: '', //10
179
      /** @type {string} */
180
      originationTime: '', //8
181
      /**
182
       * 2 32-bit values, timeReference high and low
183
       * @type {!Array<number>}
184
       */
185
      timeReference: [0, 0],
186
      /** @type {number} */
187
      version: 0, //WORD
188
      /** @type {string} */
189
      UMID: '', // 64 chars
190
      /** @type {number} */
191
      loudnessValue: 0, //WORD
192
      /** @type {number} */
193
      loudnessRange: 0, //WORD
194
      /** @type {number} */
195
      maxTruePeakLevel: 0, //WORD
196
      /** @type {number} */
197
      maxMomentaryLoudness: 0, //WORD
198
      /** @type {number} */
199
      maxShortTermLoudness: 0, //WORD
200
      /** @type {string} */
201
      reserved: '', //180
202
      /** @type {string} */
203
      codingHistory: '' // string, unlimited
204
    };
205
    /**
206
     * The data of the 'ds64' chunk.
207
     * Used only with RF64 files.
208
     * @type {!Object<string, *>}
209
     */
210
    this.ds64 = {
211
      /** @type {string} */
212
      chunkId: '',
213
      /** @type {number} */
214
      chunkSize: 0,
215
      /** @type {number} */
216
      riffSizeHigh: 0, // DWORD
217
      /** @type {number} */
218
      riffSizeLow: 0, // DWORD
219
      /** @type {number} */
220
      dataSizeHigh: 0, // DWORD
221
      /** @type {number} */
222
      dataSizeLow: 0, // DWORD
223
      /** @type {number} */
224
      originationTime: 0, // DWORD
225
      /** @type {number} */
226
      sampleCountHigh: 0, // DWORD
227
      /** @type {number} */
228
      sampleCountLow: 0 // DWORD
229
      /** @type {number} */
230
      //'tableLength': 0, // DWORD
231
      /** @type {!Array<number>} */
232
      //'table': []
233
    };
234
    /**
235
     * The data of the 'data' chunk.
236
     * @type {!Object<string, *>}
237
     */
238
    this.data = {
239
      /** @type {string} */
240
      chunkId: '',
241
      /** @type {number} */
242
      chunkSize: 0,
243
      /** @type {!Uint8Array} */
244
      samples: new Uint8Array(0)
245
    };
246
    /**
247
     * The data of the 'LIST' chunks.
248
     * Each item in this list look like this:
249
     *  {
250
     *      chunkId: '',
251
     *      chunkSize: 0,
252
     *      format: '',
253
     *      subChunks: []
254
     *   }
255
     * @type {!Array<!Object>}
256
     */
257
    this.LIST = [];
258
    /**
259
     * The data of the 'junk' chunk.
260
     * @type {!Object<string, *>}
261
     */
262
    this.junk = {
263
      /** @type {string} */
264
      chunkId: '',
265
      /** @type {number} */
266
      chunkSize: 0,
267
      /** @type {!Array<number>} */
268
      chunkData: []
269
    };
270
    /**
271
     * The bit depth code according to the samples.
272
     * @type {string}
273
     */
274
    this.bitDepth = '0';
275
    /**
276
     * @type {!Object}
277
     * @private
278
     */
279
    this.dataType = {};
280
    // Load a file from the buffer if one was passed
281
    // when creating the object
282
    if (wavBuffer) {
283
      this.fromBuffer(wavBuffer);
284
    }
285
  }
286
287
  /**
288
   * Return the sample at a given index.
289
   * @param {number} index The sample index.
290
   * @return {number} The sample.
291
   * @throws {Error} If the sample index is off range.
292
   */
293
  getSample(index) {
294
    index = index * (this.dataType.bits / 8);
295
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
296
      throw new Error('Range error');
297
    }
298
    return unpack(
299
      this.data.samples.slice(index, index + this.dataType.bits / 8),
300
      this.dataType);
301
  }
302
303
  /**
304
   * Set the sample at a given index.
305
   * @param {number} index The sample index.
306
   * @param {number} sample The sample.
307
   * @throws {Error} If the sample index is off range.
308
   */
309
  setSample(index, sample) {
310
    index = index * (this.dataType.bits / 8);
311
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
312
      throw new Error('Range error');
313
    }
314
    packTo(sample, this.dataType, this.data.samples, index);
315
  }
316
317
  /**
318
   * Set up the WaveFile object based on the arguments passed.
319
   * @param {number} numChannels The number of channels
320
   *    (Integer numbers: 1 for mono, 2 stereo and so on).
321
   * @param {number} sampleRate The sample rate.
322
   *    Integer numbers like 8000, 44100, 48000, 96000, 192000.
323
   * @param {string} bitDepthCode The audio bit depth code.
324
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
325
   *    or any value between '8' and '32' (like '12').
326
   * @param {!Array<number>|!Array<!Array<number>>|!TypedArray} samples
327
   *    The samples. Must be in the correct range according to the bit depth.
328
   * @param {?Object} options Optional. Used to force the container
329
   *    as RIFX with {'container': 'RIFX'}
330
   * @throws {Error} If any argument does not meet the criteria.
331
   */
332
  fromScratch(numChannels, sampleRate, bitDepthCode, samples, options={}) {
333
    if (!options.container) {
334
      options.container = 'RIFF';
335
    }
336
    this.container = options.container;
337
    this.bitDepth = bitDepthCode;
338
    samples = this.interleave_(samples);
339
    this.updateDataType_();
340
    /** @type {number} */
341
    let numBytes = this.dataType.bits / 8;
342
    this.data.samples = new Uint8Array(samples.length * numBytes);
343
    packArrayTo(samples, this.dataType, this.data.samples);
344
    this.clearHeader_();
345
    this.makeWavHeader(
346
      bitDepthCode, numChannels, sampleRate,
347
      numBytes, this.data.samples.length, options);
348
    this.data.chunkId = 'data';
349
    this.data.chunkSize = this.data.samples.length;
350
    this.validateWavHeader_();
351
  }
352
353
  /**
354
   * Set up the WaveFile object from a byte buffer.
355
   * @param {!Uint8Array} bytes The buffer.
356
   * @param {boolean=} samples True if the samples should be loaded.
357
   * @throws {Error} If container is not RIFF, RIFX or RF64.
358
   * @throws {Error} If no 'fmt ' chunk is found.
359
   * @throws {Error} If no 'data' chunk is found.
360
   */
361
  fromBuffer(bytes, samples=true) {
362
    this.clearHeader_();
363
    this.readWavBuffer(bytes, samples);
364
    this.bitDepthFromFmt_();
365
    this.updateDataType_();
366
  }
367
368
  /**
369
   * Return a byte buffer representig the WaveFile object as a .wav file.
370
   * The return value of this method can be written straight to disk.
371
   * @return {!Uint8Array} A .wav file.
372
   * @throws {Error} If any property of the object appears invalid.
373
   */
374
  toBuffer() {
375
    this.validateWavHeader_();
376
    return this.writeWavBuffer();
377
  }
378
379
  /**
380
   * Use a .wav file encoded as a base64 string to load the WaveFile object.
381
   * @param {string} base64String A .wav file as a base64 string.
382
   * @throws {Error} If any property of the object appears invalid.
383
   */
384
  fromBase64(base64String) {
385
    this.fromBuffer(new Uint8Array(decode(base64String)));
386
  }
387
388
  /**
389
   * Return a base64 string representig the WaveFile object as a .wav file.
390
   * @return {string} A .wav file as a base64 string.
391
   * @throws {Error} If any property of the object appears invalid.
392
   */
393
  toBase64() {
394
    /** @type {!Uint8Array} */
395
    let buffer = this.toBuffer();
396
    return encode(buffer, 0, buffer.length);
397
  }
398
399
  /**
400
   * Return a DataURI string representig the WaveFile object as a .wav file.
401
   * The return of this method can be used to load the audio in browsers.
402
   * @return {string} A .wav file as a DataURI.
403
   * @throws {Error} If any property of the object appears invalid.
404
   */
405
  toDataURI() {
406
    return 'data:audio/wav;base64,' + this.toBase64();
407
  }
408
409
  /**
410
   * Use a .wav file encoded as a DataURI to load the WaveFile object.
411
   * @param {string} dataURI A .wav file as DataURI.
412
   * @throws {Error} If any property of the object appears invalid.
413
   */
414
  fromDataURI(dataURI) {
415
    this.fromBase64(dataURI.replace('data:audio/wav;base64,', ''));
416
  }
417
418
  /**
419
   * Force a file as RIFF.
420
   */
421
  toRIFF() {
422
    this.fromScratch(
423
      this.fmt.numChannels,
424
      this.fmt.sampleRate,
425
      this.bitDepth,
426
      unpackArray(this.data.samples, this.dataType));
427
  }
428
429
  /**
430
   * Force a file as RIFX.
431
   */
432
  toRIFX() {
433
    this.fromScratch(
434
      this.fmt.numChannels,
435
      this.fmt.sampleRate,
436
      this.bitDepth,
437
      unpackArray(this.data.samples, this.dataType),
438
      {container: 'RIFX'});
439
  }
440
441
  /**
442
   * Change the bit depth of the samples.
443
   * @param {string} newBitDepth The new bit depth of the samples.
444
   *    One of '8' ... '32' (integers), '32f' or '64' (floats)
445
   * @param {boolean} changeResolution A boolean indicating if the
446
   *    resolution of samples should be actually changed or not.
447
   * @throws {Error} If the bit depth is not valid.
448
   */
449
  toBitDepth(newBitDepth, changeResolution=true) {
450
    /** @type {string} */
451
    let toBitDepth = newBitDepth;
452
    /** @type {string} */
453
    let thisBitDepth = this.bitDepth;
454
    if (!changeResolution) {
455
      if (newBitDepth != '32f') {
456
        toBitDepth = this.dataType.bits.toString();
457
      }
458
      thisBitDepth = this.dataType.bits;
459
    }
460
    this.assureUncompressed_();
461
    /** @type {number} */
462
    let sampleCount = this.data.samples.length / (this.dataType.bits / 8);
463
    /** @type {!Float64Array} */
464
    let typedSamplesInput = new Float64Array(sampleCount);
465
    /** @type {!Float64Array} */
466
    let typedSamplesOutput = new Float64Array(sampleCount);
467
    unpackArrayTo(this.data.samples, this.dataType, typedSamplesInput);
468
    if (thisBitDepth == "32f" || thisBitDepth == "64") {
469
      this.truncateSamples_(typedSamplesInput);
470
    }
471
    bitDepthLib(
472
      typedSamplesInput, thisBitDepth, toBitDepth, typedSamplesOutput);
473
    this.fromScratch(
474
      this.fmt.numChannels,
475
      this.fmt.sampleRate,
476
      newBitDepth,
477
      typedSamplesOutput,
478
      {container: this.correctContainer_()});
479
  }
480
481
  /**
482
   * Encode a 16-bit wave file as 4-bit IMA ADPCM.
483
   * @throws {Error} If sample rate is not 8000.
484
   * @throws {Error} If number of channels is not 1.
485
   */
486
  toIMAADPCM() {
487
    if (this.fmt.sampleRate !== 8000) {
488
      throw new Error(
489
        'Only 8000 Hz files can be compressed as IMA-ADPCM.');
490
    } else if (this.fmt.numChannels !== 1) {
491
      throw new Error(
492
        'Only mono files can be compressed as IMA-ADPCM.');
493
    } else {
494
      this.assure16Bit_();
495
      /** @type {!Int16Array} */
496
      let output = new Int16Array(this.data.samples.length / 2);
497
      unpackArrayTo(this.data.samples, this.dataType, output);
498
      this.fromScratch(
499
        this.fmt.numChannels,
500
        this.fmt.sampleRate,
501
        '4',
502
        imaadpcm.encode(output),
503
        {container: this.correctContainer_()});
504
    }
505
  }
506
507
  /**
508
   * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
509
   * @param {string} bitDepthCode The new bit depth of the samples.
510
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
511
   *    Optional. Default is 16.
512
   */
513
  fromIMAADPCM(bitDepthCode='16') {
514
    this.fromScratch(
515
      this.fmt.numChannels,
516
      this.fmt.sampleRate,
517
      '16',
518
      imaadpcm.decode(this.data.samples, this.fmt.blockAlign),
519
      {container: this.correctContainer_()});
520
    if (bitDepthCode != '16') {
521
      this.toBitDepth(bitDepthCode);
522
    }
523
  }
524
525
  /**
526
   * Encode a 16-bit wave file as 8-bit A-Law.
527
   */
528
  toALaw() {
529
    this.assure16Bit_();
530
    /** @type {!Int16Array} */
531
    let output = new Int16Array(this.data.samples.length / 2);
532
    unpackArrayTo(this.data.samples, this.dataType, output);
533
    this.fromScratch(
534
      this.fmt.numChannels,
535
      this.fmt.sampleRate,
536
      '8a',
537
      alawmulaw.alaw.encode(output),
538
      {container: this.correctContainer_()});
539
  }
540
541
  /**
542
   * Decode a 8-bit A-Law wave file into a 16-bit wave file.
543
   * @param {string} bitDepthCode The new bit depth of the samples.
544
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
545
   *    Optional. Default is 16.
546
   */
547
  fromALaw(bitDepthCode='16') {
548
    this.fromScratch(
549
      this.fmt.numChannels,
550
      this.fmt.sampleRate,
551
      '16',
552
      alawmulaw.alaw.decode(this.data.samples),
553
      {container: this.correctContainer_()});
554
    if (bitDepthCode != '16') {
555
      this.toBitDepth(bitDepthCode);
556
    }
557
  }
558
559
  /**
560
   * Encode 16-bit wave file as 8-bit mu-Law.
561
   */
562
  toMuLaw() {
563
    this.assure16Bit_();
564
    /** @type {!Int16Array} */
565
    let output = new Int16Array(this.data.samples.length / 2);
566
    unpackArrayTo(this.data.samples, this.dataType, output);
567
    this.fromScratch(
568
      this.fmt.numChannels,
569
      this.fmt.sampleRate,
570
      '8m',
571
      alawmulaw.mulaw.encode(output),
572
      {container: this.correctContainer_()});
573
  }
574
575
  /**
576
   * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
577
   * @param {string} bitDepthCode The new bit depth of the samples.
578
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
579
   *    Optional. Default is 16.
580
   */
581
  fromMuLaw(bitDepthCode='16') {
582
    this.fromScratch(
583
      this.fmt.numChannels,
584
      this.fmt.sampleRate,
585
      '16',
586
      alawmulaw.mulaw.decode(this.data.samples),
587
      {container: this.correctContainer_()});
588
    if (bitDepthCode != '16') {
589
      this.toBitDepth(bitDepthCode);
590
    }
591
  }
592
593
  /**
594
   * Write a RIFF tag in the INFO chunk. If the tag do not exist,
595
   * then it is created. It if exists, it is overwritten.
596
   * @param {string} tag The tag name.
597
   * @param {string} value The tag value.
598
   * @throws {Error} If the tag name is not valid.
599
   */
600
  setTag(tag, value) {
601
    tag = this.fixTagName_(tag);
602
    /** @type {!Object} */
603
    let index = this.getTagIndex_(tag);
604
    if (index.TAG !== null) {
605
      this.LIST[index.LIST].subChunks[index.TAG].chunkSize =
606
        value.length + 1;
607
      this.LIST[index.LIST].subChunks[index.TAG].value = value;
608
    } else if (index.LIST !== null) {
609
      this.LIST[index.LIST].subChunks.push({
610
        chunkId: tag,
611
        chunkSize: value.length + 1,
612
        value: value});
613
    } else {
614
      this.LIST.push({
615
        chunkId: 'LIST',
616
        chunkSize: 8 + value.length + 1,
617
        format: 'INFO',
618
        subChunks: []});
619
      this.LIST[this.LIST.length - 1].subChunks.push({
620
        chunkId: tag,
621
        chunkSize: value.length + 1,
622
        value: value});
623
    }
624
  }
625
626
  /**
627
   * Return the value of a RIFF tag in the INFO chunk.
628
   * @param {string} tag The tag name.
629
   * @return {?string} The value if the tag is found, null otherwise.
630
   */
631
  getTag(tag) {
632
    /** @type {!Object} */
633
    let index = this.getTagIndex_(tag);
634
    if (index.TAG !== null) {
635
      return this.LIST[index.LIST].subChunks[index.TAG].value;
636
    }
637
    return null;
638
  }
639
640
  /**
641
   * Return a Object<tag, value> with the RIFF tags in the file.
642
   * @return {!Object<string, string>} The file tags.
643
   */
644
  listTags() {
645
    /** @type {?number} */
646
    let index = this.getLISTINFOIndex_();
647
    /** @type {!Object} */
648
    let tags = {};
649
    if (index !== null) {
650
      for (let i = 0, len = this.LIST[index].subChunks.length; i < len; i++) {
651
        tags[this.LIST[index].subChunks[i].chunkId] =
652
          this.LIST[index].subChunks[i].value;
653
      }
654
    }
655
    return tags;
656
  }
657
658
  /**
659
   * Remove a RIFF tag in the INFO chunk.
660
   * @param {string} tag The tag name.
661
   * @return {boolean} True if a tag was deleted.
662
   */
663
  deleteTag(tag) {
664
    /** @type {!Object} */
665
    let index = this.getTagIndex_(tag);
666
    if (index.TAG !== null) {
667
      this.LIST[index.LIST].subChunks.splice(index.TAG, 1);
668
      return true;
669
    }
670
    return false;
671
  }
672
673
  /**
674
   * Create a cue point in the wave file.
675
   * @param {number} position The cue point position in milliseconds.
676
   * @param {string} labl The LIST adtl labl text of the marker. Optional.
677
   */
678
  setCuePoint(position, labl='') {
679
    this.cue.chunkId = 'cue ';
680
    position = (position * this.fmt.sampleRate) / 1000;
681
    /** @type {!Array<!Object>} */
682
    let existingPoints = this.getCuePoints_();
683
    this.clearLISTadtl_();
684
    /** @type {number} */
685
    let len = this.cue.points.length;
686
    this.cue.points = [];
687
    /** @type {boolean} */
688
    let hasSet = false;
689
    if (len === 0) {
690
      this.setCuePoint_(position, 1, labl);
691
    } else {
692
      for (let i = 0; i < len; i++) {
693
        if (existingPoints[i].dwPosition > position && !hasSet) {
694
          this.setCuePoint_(position, i + 1, labl);
695
          this.setCuePoint_(
696
            existingPoints[i].dwPosition,
697
            i + 2,
698
            existingPoints[i].label);
699
          hasSet = true;
700
        } else {
701
          this.setCuePoint_(
702
            existingPoints[i].dwPosition,
703
            i + 1,
704
            existingPoints[i].label);
705
        }
706
      }
707
      if (!hasSet) {
708
        this.setCuePoint_(position, this.cue.points.length + 1, labl);
709
      }
710
    }
711
    this.cue.dwCuePoints = this.cue.points.length;
712
  }
713
714
  /**
715
   * Remove a cue point from a wave file.
716
   * @param {number} index the index of the point. First is 1,
717
   *    second is 2, and so on.
718
   */
719
  deleteCuePoint(index) {
720
    this.cue.chunkId = 'cue ';
721
    /** @type {!Array<!Object>} */
722
    let existingPoints = this.getCuePoints_();
723
    this.clearLISTadtl_();
724
    /** @type {number} */
725
    let len = this.cue.points.length;
726
    this.cue.points = [];
727
    for (let i = 0; i < len; i++) {
728
      if (i + 1 !== index) {
729
        this.setCuePoint_(
730
          existingPoints[i].dwPosition,
731
          i + 1,
732
          existingPoints[i].label);
733
      }
734
    }
735
    this.cue.dwCuePoints = this.cue.points.length;
736
    if (this.cue.dwCuePoints) {
737
      this.cue.chunkId = 'cue ';
738
    } else {
739
      this.cue.chunkId = '';
740
      this.clearLISTadtl_();
741
    }
742
  }
743
744
  /**
745
   * Return an array with all cue points in the file, in the order they appear
746
   * in the file.
747
   * The difference between this method and using the list in WaveFile.cue
748
   * is that the return value of this method includes the position in
749
   * milliseconds of each cue point (WaveFile.cue only have the sample offset)
750
   * @return {!Array<!Object>}
751
   */
752
  listCuePoints() {
753
    /** @type {!Array<!Object>} */
754
    let points = this.getCuePoints_();
755
    for (let i = 0, len = points.length; i < len; i++) {
756
      points[i].milliseconds =
757
        (points[i].dwPosition / this.fmt.sampleRate) * 1000;
758
    }
759
    return points;
760
  }
761
762
  /**
763
   * Update the label of a cue point.
764
   * @param {number} pointIndex The ID of the cue point.
765
   * @param {string} label The new text for the label.
766
   */
767
  updateLabel(pointIndex, label) {
768
    /** @type {?number} */
769
    let cIndex = this.getAdtlChunk_();
770
    if (cIndex !== null) {
771
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
772
        if (this.LIST[cIndex].subChunks[i].dwName ==
773
            pointIndex) {
774
          this.LIST[cIndex].subChunks[i].value = label;
775
        }
776
      }
777
    }
778
  }
779
780
  /**
781
   * Set the string code of the bit depth based on the 'fmt ' chunk.
782
   * @private
783
   */
784
  bitDepthFromFmt_() {
785
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
786
      this.bitDepth = '32f';
787
    } else if (this.fmt.audioFormat === 6) {
788
      this.bitDepth = '8a';
789
    } else if (this.fmt.audioFormat === 7) {
790
      this.bitDepth = '8m';
791
    } else {
792
      this.bitDepth = this.fmt.bitsPerSample.toString();
793
    }
794
  }
795
  
796
  /**
797
   * Push a new cue point in this.cue.points.
798
   * @param {number} position The position in milliseconds.
799
   * @param {number} dwName the dwName of the cue point
800
   * @private
801
   */
802
  setCuePoint_(position, dwName, label) {
803
    this.cue.points.push({
804
      dwName: dwName,
805
      dwPosition: position,
806
      fccChunk: 'data',
807
      dwChunkStart: 0,
808
      dwBlockStart: 0,
809
      dwSampleOffset: position,
810
    });
811
    this.setLabl_(dwName, label);
812
  }
813
814
  /**
815
   * Return an array with all cue points in the file, in the order they appear
816
   * in the file.
817
   * @return {!Array<!Object>}
818
   * @private
819
   */
820
  getCuePoints_() {
821
    /** @type {!Array<!Object>} */
822
    let points = [];
823
    for (let i = 0, len = this.cue.points.length; i < len; i++) {
824
      points.push({
825
        dwPosition: this.cue.points[i].dwPosition,
826
        label: this.getLabelForCuePoint_(
827
          this.cue.points[i].dwName)});
828
    }
829
    return points;
830
  }
831
832
  /**
833
   * Return the label of a cue point.
834
   * @param {number} pointDwName The ID of the cue point.
835
   * @return {string}
836
   * @private
837
   */
838
  getLabelForCuePoint_(pointDwName) {
839
    /** @type {?number} */
840
    let cIndex = this.getAdtlChunk_();
841
    if (cIndex !== null) {
842
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
843
        if (this.LIST[cIndex].subChunks[i].dwName ==
844
            pointDwName) {
845
          return this.LIST[cIndex].subChunks[i].value;
846
        }
847
      }
848
    }
849
    return '';
850
  }
851
852
  /**
853
   * Clear any LIST chunk labeled as 'adtl'.
854
   * @private
855
   */
856
  clearLISTadtl_() {
857
    for (let i = 0, len = this.LIST.length; i < len; i++) {
858
      if (this.LIST[i].format == 'adtl') {
859
        this.LIST.splice(i);
860
      }
861
    }
862
  }
863
864
  /**
865
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
866
   * @param {number} dwName The ID of the cue point.
867
   * @param {string} label The label for the cue point.
868
   * @private
869
   */
870
  setLabl_(dwName, label) {
871
    /** @type {?number} */
872
    let adtlIndex = this.getAdtlChunk_();
873
    if (adtlIndex === null) {
874
      this.LIST.push({
875
        chunkId: 'LIST',
876
        chunkSize: 4,
877
        format: 'adtl',
878
        subChunks: []});
879
      adtlIndex = this.LIST.length - 1;
880
    }
881
    this.setLabelText_(adtlIndex === null ? 0 : adtlIndex, dwName, label);
882
  }
883
884
  /**
885
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
886
   * @param {number} adtlIndex The index of the 'adtl' LIST in this.LIST.
887
   * @param {number} dwName The ID of the cue point.
888
   * @param {string} label The label for the cue point.
889
   * @private
890
   */
891
  setLabelText_(adtlIndex, dwName, label) {
892
    this.LIST[adtlIndex].subChunks.push({
893
      chunkId: 'labl',
894
      chunkSize: label.length,
895
      dwName: dwName,
896
      value: label
897
    });
898
    this.LIST[adtlIndex].chunkSize += label.length + 4 + 4 + 4 + 1;
899
  }
900
901
  /**
902
   * Return the index of the 'adtl' LIST in this.LIST.
903
   * @return {?number}
904
   * @private
905
   */
906
  getAdtlChunk_() {
907
    for (let i = 0, len = this.LIST.length; i < len; i++) {
908
      if (this.LIST[i].format == 'adtl') {
909
        return i;
910
      }
911
    }
912
    return null;
913
  }
914
915
  /**
916
   * Return the index of the INFO chunk in the LIST chunk.
917
   * @return {?number} the index of the INFO chunk.
918
   * @private
919
   */
920
  getLISTINFOIndex_() {
921
    /** @type {?number} */
922
    let index = null;
923
    for (let i = 0, len = this.LIST.length; i < len; i++) {
924
      if (this.LIST[i].format === 'INFO') {
925
        index = i;
926
        break;
927
      }
928
    }
929
    return index;
930
  }
931
932
  /**
933
   * Return the index of a tag in a FILE chunk.
934
   * @param {string} tag The tag name.
935
   * @return {!Object<string, ?number>}
936
   *    Object.LIST is the INFO index in LIST
937
   *    Object.TAG is the tag index in the INFO
938
   * @private
939
   */
940
  getTagIndex_(tag) {
941
    /** @type {!Object<string, ?number>} */
942
    let index = {LIST: null, TAG: null};
943
    for (let i = 0, len = this.LIST.length; i < len; i++) {
944
      if (this.LIST[i].format == 'INFO') {
945
        index.LIST = i;
946
        for (let j=0, subLen = this.LIST[i].subChunks.length; j < subLen; j++) {
947
          if (this.LIST[i].subChunks[j].chunkId == tag) {
948
            index.TAG = j;
949
            break;
950
          }
951
        }
952
        break;
953
      }
954
    }
955
    return index;
956
  }
957
958
  /**
959
   * Fix a RIFF tag format if possible, throw an error otherwise.
960
   * @param {string} tag The tag name.
961
   * @return {string} The tag name in proper fourCC format.
962
   * @private
963
   */
964
  fixTagName_(tag) {
965
    if (tag.constructor !== String) {
966
      throw new Error('Invalid tag name.');
967
    } else if (tag.length < 4) {
968
      for (let i = 0, len = 4 - tag.length; i < len; i++) {
969
        tag += ' ';
970
      }
971
    }
972
    return tag;
973
  }
974
975
  /**
976
   * Reset attributes that should emptied when a file is
977
   * created with the fromScratch() or fromBuffer() methods.
978
   * @private
979
   */
980
  clearHeader_() {
981
    this.fmt.cbSize = 0;
982
    this.fmt.validBitsPerSample = 0;
983
    this.fact.chunkId = '';
984
    this.ds64.chunkId = '';
985
  }
986
987
  /**
988
   * Make the file 16-bit if it is not.
989
   * @private
990
   */
991
  assure16Bit_() {
992
    this.assureUncompressed_();
993
    if (this.bitDepth != '16') {
994
      this.toBitDepth('16');
995
    }
996
  }
997
998
  /**
999
   * Uncompress the samples in case of a compressed file.
1000
   * @private
1001
   */
1002
  assureUncompressed_() {
1003
    if (this.bitDepth == '8a') {
1004
      this.fromALaw();
1005
    } else if (this.bitDepth == '8m') {
1006
      this.fromMuLaw();
1007
    } else if (this.bitDepth == '4') {
1008
      this.fromIMAADPCM();
1009
    }
1010
  }
1011
1012
  /**
1013
   * Interleave de-interleaved samples.
1014
   * @param {!Array<number>|!Array<!Array<number>>|!TypedArray} samples
1015
   *    The samples.
1016
   * @return {!Array<number>|!Array<!Array<number>>|!TypedArray}
1017
   * @private
1018
   */
1019
  interleave_(samples) {
1020
    if (samples.length > 0) {
1021
      if (samples[0].constructor === Array) {
1022
        /** @type {!Array<number>} */
1023
        let finalSamples = [];
1024
        for (let i = 0, len = samples[0].length; i < len; i++) {
1025
          for (let j = 0, subLen = samples.length; j < subLen; j++) {
1026
            finalSamples.push(samples[j][i]);
1027
          }
1028
        }
1029
        samples = finalSamples;
1030
      }
1031
    }
1032
    return samples;
1033
  }
1034
1035
  /**
1036
   * Update the type definition used to read and write the samples.
1037
   * @private
1038
   */
1039
  updateDataType_() {
1040
    this.dataType = {
1041
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
1042
      fp: this.bitDepth == '32f' || this.bitDepth == '64',
1043
      signed: this.bitDepth != '8',
1044
      be: this.container == 'RIFX'
1045
    };
1046
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
1047
      this.dataType.bits = 8;
1048
      this.dataType.signed = false;
1049
    }
1050
  }
1051
1052
  /**
1053
   * Return 'RIFF' if the container is 'RF64', the current container name
1054
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
1055
   * @return {string}
1056
   * @private
1057
   */
1058
  correctContainer_() {
1059
    return this.container == 'RF64' ? 'RIFF' : this.container;
1060
  }
1061
1062
  /**
1063
   * Truncate float samples on over and underflow.
1064
   * @private
1065
   */
1066
  truncateSamples_(samples) {
1067
    for (let i = 0, len = samples.length; i < len; i++) {
1068
      if (samples[i] > 1) {
1069
        samples[i] = 1;
1070
      } else if (samples[i] < -1) {
1071
        samples[i] = -1;
1072
      }
1073
    }
1074
  }
1075
1076
  /**
1077
   * Return a .wav file byte buffer with the data from the WaveFile object.
1078
   * The return value of this method can be written straight to disk.
1079
   * @return {!Uint8Array} The wav file bytes.
1080
   * @private
1081
   */
1082
  writeWavBuffer() {
1083
    this.uInt16_.be = this.container === 'RIFX';
1084
    this.uInt32_.be = this.uInt16_.be;
1085
    /** @type {!Array<!Array<number>>} */
1086
    let fileBody = [
1087
      this.getJunkBytes_(),
1088
      this.getDs64Bytes_(),
1089
      this.getBextBytes_(),
1090
      this.getFmtBytes_(),
1091
      this.getFactBytes_(),
1092
      packString(this.data.chunkId),
1093
      pack(this.data.samples.length, this.uInt32_),
1094
      this.data.samples,
1095
      this.getCueBytes_(),
1096
      this.getSmplBytes_(),
1097
      this.getLISTBytes_()
1098
    ];
1099
    /** @type {number} */
1100
    let fileBodyLength = 0;
1101
    for (let i=0; i<fileBody.length; i++) {
1102
      fileBodyLength += fileBody[i].length;
1103
    }
1104
    /** @type {!Uint8Array} */
1105
    let file = new Uint8Array(fileBodyLength + 12);
1106
    /** @type {number} */
1107
    let index = 0;
1108
    index = packStringTo(this.container, file, index);
1109
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
1110
    index = packStringTo(this.format, file, index);
1111
    for (let i=0; i<fileBody.length; i++) {
1112
      file.set(fileBody[i], index);
1113
      index += fileBody[i].length;
1114
    }
1115
    return file;
1116
  }
1117
1118
  /**
1119
   * Return the bytes of the 'bext' chunk.
1120
   * @private
1121
   */
1122
  getBextBytes_() {
1123
    /** @type {!Array<number>} */
1124
    let bytes = [];
1125
    this.enforceBext_();
1126
    if (this.bext.chunkId) {
1127
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
1128
      bytes = bytes.concat(
1129
        packString(this.bext.chunkId),
1130
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
1131
        writeString(this.bext.description, 256),
1132
        writeString(this.bext.originator, 32),
1133
        writeString(this.bext.originatorReference, 32),
1134
        writeString(this.bext.originationDate, 10),
1135
        writeString(this.bext.originationTime, 8),
1136
        pack(this.bext.timeReference[0], this.uInt32_),
1137
        pack(this.bext.timeReference[1], this.uInt32_),
1138
        pack(this.bext.version, this.uInt16_),
1139
        writeString(this.bext.UMID, 64),
1140
        pack(this.bext.loudnessValue, this.uInt16_),
1141
        pack(this.bext.loudnessRange, this.uInt16_),
1142
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
1143
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
1144
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
1145
        writeString(this.bext.reserved, 180),
1146
        writeString(
1147
          this.bext.codingHistory, this.bext.codingHistory.length));
1148
    }
1149
    return bytes;
1150
  }
1151
1152
  /**
1153
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
1154
   * @private
1155
   */
1156
  enforceBext_() {
1157
    for (let prop in this.bext) {
1158
      if (this.bext.hasOwnProperty(prop)) {
1159
        if (this.bext[prop] && prop != 'timeReference') {
1160
          this.bext.chunkId = 'bext';
1161
          break;
1162
        }
1163
      }
1164
    }
1165
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
1166
      this.bext.chunkId = 'bext';
1167
    }
1168
  }
1169
1170
  /**
1171
   * Return the bytes of the 'ds64' chunk.
1172
   * @return {!Array<number>} The 'ds64' chunk bytes.
1173
   * @private
1174
   */
1175
  getDs64Bytes_() {
1176
    /** @type {!Array<number>} */
1177
    let bytes = [];
1178
    if (this.ds64.chunkId) {
1179
      bytes = bytes.concat(
1180
        packString(this.ds64.chunkId),
1181
        pack(this.ds64.chunkSize, this.uInt32_),
1182
        pack(this.ds64.riffSizeHigh, this.uInt32_),
1183
        pack(this.ds64.riffSizeLow, this.uInt32_),
1184
        pack(this.ds64.dataSizeHigh, this.uInt32_),
1185
        pack(this.ds64.dataSizeLow, this.uInt32_),
1186
        pack(this.ds64.originationTime, this.uInt32_),
1187
        pack(this.ds64.sampleCountHigh, this.uInt32_),
1188
        pack(this.ds64.sampleCountLow, this.uInt32_));
1189
    }
1190
    //if (this.ds64.tableLength) {
1191
    //  ds64Bytes = ds64Bytes.concat(
1192
    //    pack(this.ds64.tableLength, this.uInt32_),
1193
    //    this.ds64.table);
1194
    //}
1195
    return bytes;
1196
  }
1197
1198
  /**
1199
   * Return the bytes of the 'cue ' chunk.
1200
   * @return {!Array<number>} The 'cue ' chunk bytes.
1201
   * @private
1202
   */
1203
  getCueBytes_() {
1204
    /** @type {!Array<number>} */
1205
    let bytes = [];
1206
    if (this.cue.chunkId) {
1207
      /** @type {!Array<number>} */
1208
      let cuePointsBytes = this.getCuePointsBytes_();
1209
      bytes = bytes.concat(
1210
        packString(this.cue.chunkId),
1211
        pack(cuePointsBytes.length + 4, this.uInt32_),
1212
        pack(this.cue.dwCuePoints, this.uInt32_),
1213
        cuePointsBytes);
1214
    }
1215
    return bytes;
1216
  }
1217
1218
  /**
1219
   * Return the bytes of the 'cue ' points.
1220
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
1221
   * @private
1222
   */
1223
  getCuePointsBytes_() {
1224
    /** @type {!Array<number>} */
1225
    let points = [];
1226
    for (let i=0; i<this.cue.dwCuePoints; i++) {
1227
      points = points.concat(
1228
        pack(this.cue.points[i].dwName, this.uInt32_),
1229
        pack(this.cue.points[i].dwPosition, this.uInt32_),
1230
        packString(this.cue.points[i].fccChunk),
1231
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
1232
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
1233
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
1234
    }
1235
    return points;
1236
  }
1237
1238
  /**
1239
   * Return the bytes of the 'smpl' chunk.
1240
   * @return {!Array<number>} The 'smpl' chunk bytes.
1241
   * @private
1242
   */
1243
  getSmplBytes_() {
1244
    /** @type {!Array<number>} */
1245
    let bytes = [];
1246
    if (this.smpl.chunkId) {
1247
      /** @type {!Array<number>} */
1248
      let smplLoopsBytes = this.getSmplLoopsBytes_();
1249
      bytes = bytes.concat(
1250
        packString(this.smpl.chunkId),
1251
        pack(smplLoopsBytes.length + 36, this.uInt32_),
1252
        pack(this.smpl.dwManufacturer, this.uInt32_),
1253
        pack(this.smpl.dwProduct, this.uInt32_),
1254
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
1255
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
1256
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
1257
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
1258
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
1259
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
1260
        pack(this.smpl.dwSamplerData, this.uInt32_),
1261
        smplLoopsBytes);
1262
    }
1263
    return bytes;
1264
  }
1265
1266
  /**
1267
   * Return the bytes of the 'smpl' loops.
1268
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
1269
   * @private
1270
   */
1271
  getSmplLoopsBytes_() {
1272
    /** @type {!Array<number>} */
1273
    let loops = [];
1274
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
1275
      loops = loops.concat(
1276
        pack(this.smpl.loops[i].dwName, this.uInt32_),
1277
        pack(this.smpl.loops[i].dwType, this.uInt32_),
1278
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
1279
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
1280
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
1281
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
1282
    }
1283
    return loops;
1284
  }
1285
1286
  /**
1287
   * Return the bytes of the 'fact' chunk.
1288
   * @return {!Array<number>} The 'fact' chunk bytes.
1289
   * @private
1290
   */
1291
  getFactBytes_() {
1292
    /** @type {!Array<number>} */
1293
    let bytes = [];
1294
    if (this.fact.chunkId) {
1295
      bytes = bytes.concat(
1296
        packString(this.fact.chunkId),
1297
        pack(this.fact.chunkSize, this.uInt32_),
1298
        pack(this.fact.dwSampleLength, this.uInt32_));
1299
    }
1300
    return bytes;
1301
  }
1302
1303
  /**
1304
   * Return the bytes of the 'fmt ' chunk.
1305
   * @return {!Array<number>} The 'fmt' chunk bytes.
1306
   * @throws {Error} if no 'fmt ' chunk is present.
1307
   * @private
1308
   */
1309
  getFmtBytes_() {
1310
    /** @type {!Array<number>} */
1311
    let fmtBytes = [];
1312
    if (this.fmt.chunkId) {
1313
      return fmtBytes.concat(
1314
        packString(this.fmt.chunkId),
1315
        pack(this.fmt.chunkSize, this.uInt32_),
1316
        pack(this.fmt.audioFormat, this.uInt16_),
1317
        pack(this.fmt.numChannels, this.uInt16_),
1318
        pack(this.fmt.sampleRate, this.uInt32_),
1319
        pack(this.fmt.byteRate, this.uInt32_),
1320
        pack(this.fmt.blockAlign, this.uInt16_),
1321
        pack(this.fmt.bitsPerSample, this.uInt16_),
1322
        this.getFmtExtensionBytes_());
1323
    }
1324
    throw Error('Could not find the "fmt " chunk');
1325
  }
1326
1327
  /**
1328
   * Return the bytes of the fmt extension fields.
1329
   * @return {!Array<number>} The fmt extension bytes.
1330
   * @private
1331
   */
1332
  getFmtExtensionBytes_() {
1333
    /** @type {!Array<number>} */
1334
    let extension = [];
1335
    if (this.fmt.chunkSize > 16) {
1336
      extension = extension.concat(
1337
        pack(this.fmt.cbSize, this.uInt16_));
1338
    }
1339
    if (this.fmt.chunkSize > 18) {
1340
      extension = extension.concat(
1341
        pack(this.fmt.validBitsPerSample, this.uInt16_));
1342
    }
1343
    if (this.fmt.chunkSize > 20) {
1344
      extension = extension.concat(
1345
        pack(this.fmt.dwChannelMask, this.uInt32_));
1346
    }
1347
    if (this.fmt.chunkSize > 24) {
1348
      extension = extension.concat(
1349
        pack(this.fmt.subformat[0], this.uInt32_),
1350
        pack(this.fmt.subformat[1], this.uInt32_),
1351
        pack(this.fmt.subformat[2], this.uInt32_),
1352
        pack(this.fmt.subformat[3], this.uInt32_));
1353
    }
1354
    return extension;
1355
  }
1356
1357
  /**
1358
   * Return the bytes of the 'LIST' chunk.
1359
   * @return {!Array<number>} The 'LIST' chunk bytes.
1360
   */
1361
  getLISTBytes_() {
1362
    /** @type {!Array<number>} */
1363
    let bytes = [];
1364
    for (let i=0; i<this.LIST.length; i++) {
1365
      /** @type {!Array<number>} */
1366
      let subChunksBytes = this.getLISTSubChunksBytes_(
1367
          this.LIST[i].subChunks, this.LIST[i].format);
1368
      bytes = bytes.concat(
1369
        packString(this.LIST[i].chunkId),
1370
        pack(subChunksBytes.length + 4, this.uInt32_),
1371
        packString(this.LIST[i].format),
1372
        subChunksBytes);
1373
    }
1374
    return bytes;
1375
  }
1376
1377
  /**
1378
   * Return the bytes of the sub chunks of a 'LIST' chunk.
1379
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
1380
   * @param {string} format The format of the 'LIST' chunk.
1381
   *    Currently supported values are 'adtl' or 'INFO'.
1382
   * @return {!Array<number>} The sub chunk bytes.
1383
   * @private
1384
   */
1385
  getLISTSubChunksBytes_(subChunks, format) {
1386
    /** @type {!Array<number>} */
1387
    let bytes = [];
1388
    for (let i=0; i<subChunks.length; i++) {
1389
      if (format == 'INFO') {
1390
        bytes = bytes.concat(
1391
          packString(subChunks[i].chunkId),
1392
          pack(subChunks[i].value.length + 1, this.uInt32_),
1393
          writeString(
1394
            subChunks[i].value, subChunks[i].value.length));
1395
        bytes.push(0);
1396
      } else if (format == 'adtl') {
1397
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
1398
          bytes = bytes.concat(
1399
            packString(subChunks[i].chunkId),
1400
            pack(
1401
              subChunks[i].value.length + 4 + 1, this.uInt32_),
1402
            pack(subChunks[i].dwName, this.uInt32_),
1403
            writeString(
1404
              subChunks[i].value,
1405
              subChunks[i].value.length));
1406
          bytes.push(0);
1407
        } else if (subChunks[i].chunkId == 'ltxt') {
1408
          bytes = bytes.concat(
1409
            this.getLtxtChunkBytes_(subChunks[i]));
1410
        }
1411
      }
1412
      if (bytes.length % 2) {
1413
        bytes.push(0);
1414
      }
1415
    }
1416
    return bytes;
1417
  }
1418
1419
  /**
1420
   * Return the bytes of a 'ltxt' chunk.
1421
   * @param {!Object} ltxt the 'ltxt' chunk.
1422
   * @private
1423
   */
1424
  getLtxtChunkBytes_(ltxt) {
1425
    return [].concat(
1426
      packString(ltxt.chunkId),
1427
      pack(ltxt.value.length + 20, this.uInt32_),
1428
      pack(ltxt.dwName, this.uInt32_),
1429
      pack(ltxt.dwSampleLength, this.uInt32_),
1430
      pack(ltxt.dwPurposeID, this.uInt32_),
1431
      pack(ltxt.dwCountry, this.uInt16_),
1432
      pack(ltxt.dwLanguage, this.uInt16_),
1433
      pack(ltxt.dwDialect, this.uInt16_),
1434
      pack(ltxt.dwCodePage, this.uInt16_),
1435
      writeString(ltxt.value, ltxt.value.length));
1436
  }
1437
1438
  /**
1439
   * Return the bytes of the 'junk' chunk.
1440
   * @private
1441
   */
1442
  getJunkBytes_() {
1443
    /** @type {!Array<number>} */
1444
    let bytes = [];
1445
    if (this.junk.chunkId) {
1446
      return bytes.concat(
1447
        packString(this.junk.chunkId),
1448
        pack(this.junk.chunkData.length, this.uInt32_),
1449
        this.junk.chunkData);
1450
    }
1451
    return bytes;
1452
  }
1453
1454
  /**
1455
   * Set up the WaveFile object from a byte buffer.
1456
   * @param {!Uint8Array} wavBuffer The buffer.
1457
   * @param {boolean} samples True if the samples should be loaded.
1458
   * @throws {Error} If container is not RIFF, RIFX or RF64.
1459
   * @throws {Error} If no 'fmt ' chunk is found.
1460
   * @throws {Error} If no 'data' chunk is found.
1461
   */
1462
  readWavBuffer(wavBuffer, samples) {
1463
    this.head_ = 0;
1464
    //this.readRIFFChunk_(buffer);
1465
    //this.getSignature_(buffer);
1466
    this.loadRIFF(wavBuffer);
1467
    this.readDs64Chunk_(wavBuffer);
1468
    this.readFmtChunk_(wavBuffer);
1469
    this.readFactChunk_(wavBuffer);
1470
    this.readBextChunk_(wavBuffer);
1471
    this.readCueChunk_(wavBuffer);
1472
    this.readSmplChunk_(wavBuffer);
1473
    this.readDataChunk_(wavBuffer, samples);
1474
    this.readJunkChunk_(wavBuffer);
1475
    this.readLISTChunk_(wavBuffer);
1476
  }
1477
1478
  /**
1479
   * Read the 'fmt ' chunk of a wave file.
1480
   * @param {!Uint8Array} buffer The wav file buffer.
1481
   * @throws {Error} If no 'fmt ' chunk is found.
1482
   * @private
1483
   */
1484
  readFmtChunk_(buffer) {
1485
    /** @type {?Object} */
1486
    let chunk = this.findChunk_('fmt ');
1487
    if (chunk) {
1488
      this.head_ = chunk.chunkData.start;
1489
      this.fmt.chunkId = chunk.chunkId;
1490
      this.fmt.chunkSize = chunk.chunkSize;
1491
      this.fmt.audioFormat = this.read_(buffer, this.uInt16_);
1492
      this.fmt.numChannels = this.read_(buffer, this.uInt16_);
1493
      this.fmt.sampleRate = this.read_(buffer, this.uInt32_);
1494
      this.fmt.byteRate = this.read_(buffer, this.uInt32_);
1495
      this.fmt.blockAlign = this.read_(buffer, this.uInt16_);
1496
      this.fmt.bitsPerSample = this.read_(buffer, this.uInt16_);
1497
      this.readFmtExtension_(buffer);
1498
    } else {
1499
      throw Error('Could not find the "fmt " chunk');
1500
    }
1501
  }
1502
1503
  /**
1504
   * Read the 'fmt ' chunk extension.
1505
   * @param {!Uint8Array} buffer The wav file buffer.
1506
   * @private
1507
   */
1508
  readFmtExtension_(buffer) {
1509
    if (this.fmt.chunkSize > 16) {
1510
      this.fmt.cbSize = this.read_(buffer, this.uInt16_);
1511
      if (this.fmt.chunkSize > 18) {
1512
        this.fmt.validBitsPerSample = this.read_(buffer, this.uInt16_);
1513
        if (this.fmt.chunkSize > 20) {
1514
          this.fmt.dwChannelMask = this.read_(buffer, this.uInt32_);
1515
          this.fmt.subformat = [
1516
            this.read_(buffer, this.uInt32_),
1517
            this.read_(buffer, this.uInt32_),
1518
            this.read_(buffer, this.uInt32_),
1519
            this.read_(buffer, this.uInt32_)];
1520
        }
1521
      }
1522
    }
1523
  }
1524
1525
  /**
1526
   * Read the 'fact' chunk of a wav file.
1527
   * @param {!Uint8Array} buffer The wav file buffer.
1528
   * @private
1529
   */
1530
  readFactChunk_(buffer) {
1531
    /** @type {?Object} */
1532
    let chunk = this.findChunk_('fact');
1533
    if (chunk) {
1534
      this.head_ = chunk.chunkData.start;
1535
      this.fact.chunkId = chunk.chunkId;
1536
      this.fact.chunkSize = chunk.chunkSize;
1537
      this.fact.dwSampleLength = this.read_(buffer, this.uInt32_);
1538
    }
1539
  }
1540
1541
  /**
1542
   * Read the 'cue ' chunk of a wave file.
1543
   * @param {!Uint8Array} buffer The wav file buffer.
1544
   * @private
1545
   */
1546
  readCueChunk_(buffer) {
1547
    /** @type {?Object} */
1548
    let chunk = this.findChunk_('cue ');
1549
    if (chunk) {
1550
      this.head_ = chunk.chunkData.start;
1551
      this.cue.chunkId = chunk.chunkId;
1552
      this.cue.chunkSize = chunk.chunkSize;
1553
      this.cue.dwCuePoints = this.read_(buffer, this.uInt32_);
1554
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
1555
        this.cue.points.push({
1556
          dwName: this.read_(buffer, this.uInt32_),
1557
          dwPosition: this.read_(buffer, this.uInt32_),
1558
          fccChunk: this.readString_(buffer, 4),
1559
          dwChunkStart: this.read_(buffer, this.uInt32_),
1560
          dwBlockStart: this.read_(buffer, this.uInt32_),
1561
          dwSampleOffset: this.read_(buffer, this.uInt32_),
1562
        });
1563
      }
1564
    }
1565
  }
1566
1567
  /**
1568
   * Read the 'smpl' chunk of a wave file.
1569
   * @param {!Uint8Array} buffer The wav file buffer.
1570
   * @private
1571
   */
1572
  readSmplChunk_(buffer) {
1573
    /** @type {?Object} */
1574
    let chunk = this.findChunk_('smpl');
1575
    if (chunk) {
1576
      this.head_ = chunk.chunkData.start;
1577
      this.smpl.chunkId = chunk.chunkId;
1578
      this.smpl.chunkSize = chunk.chunkSize;
1579
      this.smpl.dwManufacturer = this.read_(buffer, this.uInt32_);
1580
      this.smpl.dwProduct = this.read_(buffer, this.uInt32_);
1581
      this.smpl.dwSamplePeriod = this.read_(buffer, this.uInt32_);
1582
      this.smpl.dwMIDIUnityNote = this.read_(buffer, this.uInt32_);
1583
      this.smpl.dwMIDIPitchFraction = this.read_(buffer, this.uInt32_);
1584
      this.smpl.dwSMPTEFormat = this.read_(buffer, this.uInt32_);
1585
      this.smpl.dwSMPTEOffset = this.read_(buffer, this.uInt32_);
1586
      this.smpl.dwNumSampleLoops = this.read_(buffer, this.uInt32_);
1587
      this.smpl.dwSamplerData = this.read_(buffer, this.uInt32_);
1588
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
1589
        this.smpl.loops.push({
1590
          dwName: this.read_(buffer, this.uInt32_),
1591
          dwType: this.read_(buffer, this.uInt32_),
1592
          dwStart: this.read_(buffer, this.uInt32_),
1593
          dwEnd: this.read_(buffer, this.uInt32_),
1594
          dwFraction: this.read_(buffer, this.uInt32_),
1595
          dwPlayCount: this.read_(buffer, this.uInt32_),
1596
        });
1597
      }
1598
    }
1599
  }
1600
1601
  /**
1602
   * Read the 'data' chunk of a wave file.
1603
   * @param {!Uint8Array} buffer The wav file buffer.
1604
   * @param {boolean} samples True if the samples should be loaded.
1605
   * @throws {Error} If no 'data' chunk is found.
1606
   * @private
1607
   */
1608
  readDataChunk_(buffer, samples) {
1609
    /** @type {?Object} */
1610
    let chunk = this.findChunk_('data');
1611
    if (chunk) {
1612
      this.data.chunkId = 'data';
1613
      this.data.chunkSize = chunk.chunkSize;
1614
      if (samples) {
1615
        this.data.samples = buffer.slice(
1616
          chunk.chunkData.start,
1617
          chunk.chunkData.end);
1618
      }
1619
    } else {
1620
      throw Error('Could not find the "data" chunk');
1621
    }
1622
  }
1623
1624
  /**
1625
   * Read the 'bext' chunk of a wav file.
1626
   * @param {!Uint8Array} buffer The wav file buffer.
1627
   * @private
1628
   */
1629
  readBextChunk_(buffer) {
1630
    /** @type {?Object} */
1631
    let chunk = this.findChunk_('bext');
1632
    if (chunk) {
1633
      this.head_ = chunk.chunkData.start;
1634
      this.bext.chunkId = chunk.chunkId;
1635
      this.bext.chunkSize = chunk.chunkSize;
1636
      this.bext.description = this.readString_(buffer, 256);
1637
      this.bext.originator = this.readString_(buffer, 32);
1638
      this.bext.originatorReference = this.readString_(buffer, 32);
1639
      this.bext.originationDate = this.readString_(buffer, 10);
1640
      this.bext.originationTime = this.readString_(buffer, 8);
1641
      this.bext.timeReference = [
1642
        this.read_(buffer, this.uInt32_),
1643
        this.read_(buffer, this.uInt32_)];
1644
      this.bext.version = this.read_(buffer, this.uInt16_);
1645
      this.bext.UMID = this.readString_(buffer, 64);
1646
      this.bext.loudnessValue = this.read_(buffer, this.uInt16_);
1647
      this.bext.loudnessRange = this.read_(buffer, this.uInt16_);
1648
      this.bext.maxTruePeakLevel = this.read_(buffer, this.uInt16_);
1649
      this.bext.maxMomentaryLoudness = this.read_(buffer, this.uInt16_);
1650
      this.bext.maxShortTermLoudness = this.read_(buffer, this.uInt16_);
1651
      this.bext.reserved = this.readString_(buffer, 180);
1652
      this.bext.codingHistory = this.readString_(
1653
        buffer, this.bext.chunkSize - 602);
1654
    }
1655
  }
1656
1657
  /**
1658
   * Read the 'ds64' chunk of a wave file.
1659
   * @param {!Uint8Array} buffer The wav file buffer.
1660
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
1661
   * @private
1662
   */
1663
  readDs64Chunk_(buffer) {
1664
    /** @type {?Object} */
1665
    let chunk = this.findChunk_('ds64');
1666
    if (chunk) {
1667
      this.head_ = chunk.chunkData.start;
1668
      this.ds64.chunkId = chunk.chunkId;
1669
      this.ds64.chunkSize = chunk.chunkSize;
1670
      this.ds64.riffSizeHigh = this.read_(buffer, this.uInt32_);
1671
      this.ds64.riffSizeLow = this.read_(buffer, this.uInt32_);
1672
      this.ds64.dataSizeHigh = this.read_(buffer, this.uInt32_);
1673
      this.ds64.dataSizeLow = this.read_(buffer, this.uInt32_);
1674
      this.ds64.originationTime = this.read_(buffer, this.uInt32_);
1675
      this.ds64.sampleCountHigh = this.read_(buffer, this.uInt32_);
1676
      this.ds64.sampleCountLow = this.read_(buffer, this.uInt32_);
1677
      //if (wav.ds64.chunkSize > 28) {
1678
      //  wav.ds64.tableLength = unpack(
1679
      //    chunkData.slice(28, 32), uInt32_);
1680
      //  wav.ds64.table = chunkData.slice(
1681
      //     32, 32 + wav.ds64.tableLength);
1682
      //}
1683
    } else {
1684
      if (this.container == 'RF64') {
1685
        throw Error('Could not find the "ds64" chunk');
1686
      }
1687
    }
1688
  }
1689
1690
  /**
1691
   * Read the 'LIST' chunks of a wave file.
1692
   * @param {!Uint8Array} buffer The wav file buffer.
1693
   * @private
1694
   */
1695
  readLISTChunk_(buffer) {
1696
    /** @type {?Object} */
1697
    let listChunks = this.findChunk_('LIST', true);
1698
    if (listChunks !== null) {
1699
      for (let j=0; j < listChunks.length; j++) {
1700
        /** @type {!Object} */
1701
        let subChunk = listChunks[j];
1702
        this.LIST.push({
1703
          chunkId: subChunk.chunkId,
1704
          chunkSize: subChunk.chunkSize,
1705
          format: subChunk.format,
1706
          subChunks: []});
1707
        for (let x=0; x<subChunk.subChunks.length; x++) {
1708
          this.readLISTSubChunks_(subChunk.subChunks[x],
1709
            subChunk.format, buffer);
1710
        }
1711
      }
1712
    }
1713
  }
1714
1715
  /**
1716
   * Read the sub chunks of a 'LIST' chunk.
1717
   * @param {!Object} subChunk The 'LIST' subchunks.
1718
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
1719
   * @param {!Uint8Array} buffer The wav file buffer.
1720
   * @private
1721
   */
1722
  readLISTSubChunks_(subChunk, format, buffer) {
1723
    if (format == 'adtl') {
1724
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
1725
        this.head_ = subChunk.chunkData.start;
1726
        /** @type {!Object<string, string|number>} */
1727
        let item = {
1728
          chunkId: subChunk.chunkId,
1729
          chunkSize: subChunk.chunkSize,
1730
          dwName: this.read_(buffer, this.uInt32_)
1731
        };
1732
        if (subChunk.chunkId == 'ltxt') {
1733
          item.dwSampleLength = this.read_(buffer, this.uInt32_);
1734
          item.dwPurposeID = this.read_(buffer, this.uInt32_);
1735
          item.dwCountry = this.read_(buffer, this.uInt16_);
1736
          item.dwLanguage = this.read_(buffer, this.uInt16_);
1737
          item.dwDialect = this.read_(buffer, this.uInt16_);
1738
          item.dwCodePage = this.read_(buffer, this.uInt16_);
1739
        }
1740
        item.value = this.readZSTR_(buffer, this.head_);
1741
        this.LIST[this.LIST.length - 1].subChunks.push(item);
1742
      }
1743
    // RIFF INFO tags like ICRD, ISFT, ICMT
1744
    } else if(format == 'INFO') {
1745
      this.head_ = subChunk.chunkData.start;
1746
      this.LIST[this.LIST.length - 1].subChunks.push({
1747
        chunkId: subChunk.chunkId,
1748
        chunkSize: subChunk.chunkSize,
1749
        value: this.readZSTR_(buffer, this.head_)
1750
      });
1751
    }
1752
  }
1753
1754
  /**
1755
   * Read the 'junk' chunk of a wave file.
1756
   * @param {!Uint8Array} buffer The wav file buffer.
1757
   * @private
1758
   */
1759
  readJunkChunk_(buffer) {
1760
    /** @type {?Object} */
1761
    let chunk = this.findChunk_('junk');
1762
    if (chunk) {
1763
      this.junk = {
1764
        chunkId: chunk.chunkId,
1765
        chunkSize: chunk.chunkSize,
1766
        chunkData: [].slice.call(buffer.slice(
1767
          chunk.chunkData.start,
1768
          chunk.chunkData.end))
1769
      };
1770
    }
1771
  }
1772
1773
  /**
1774
   * Define the header of a wav file.
1775
   * @param {string} bitDepthCode The audio bit depth
1776
   * @param {number} numChannels The number of channels
1777
   * @param {number} sampleRate The sample rate.
1778
   * @param {number} numBytes The number of bytes each sample use.
1779
   * @param {number} samplesLength The length of the samples in bytes.
1780
   * @param {!Object} options The extra options, like container defintion.
1781
   * @private
1782
   */
1783
  makeWavHeader(
1784
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1785
    if (bitDepthCode == '4') {
1786
      this.createADPCMHeader_(
1787
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1788
1789
    } else if (bitDepthCode == '8a' || bitDepthCode == '8m') {
1790
      this.createALawMulawHeader_(
1791
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1792
1793
    } else if(Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 ||
1794
        numChannels > 2) {
1795
      this.createExtensibleHeader_(
1796
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1797
1798
    } else {
1799
      this.createPCMHeader_(
1800
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);      
1801
    }
1802
  }
1803
1804
  /**
1805
   * Create the header of a linear PCM wave file.
1806
   * @param {string} bitDepthCode The audio bit depth
1807
   * @param {number} numChannels The number of channels
1808
   * @param {number} sampleRate The sample rate.
1809
   * @param {number} numBytes The number of bytes each sample use.
1810
   * @param {number} samplesLength The length of the samples in bytes.
1811
   * @param {!Object} options The extra options, like container defintion.
1812
   * @private
1813
   */
1814
  createPCMHeader_(
1815
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1816
    this.container = options.container;
1817
    this.chunkSize = 36 + samplesLength;
1818
    this.format = 'WAVE';
1819
    this.bitDepth = bitDepthCode;
1820
    this.fmt = {
1821
      chunkId: 'fmt ',
1822
      chunkSize: 16,
1823
      audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534,
1824
      numChannels: numChannels,
1825
      sampleRate: sampleRate,
1826
      byteRate: (numChannels * numBytes) * sampleRate,
1827
      blockAlign: numChannels * numBytes,
1828
      bitsPerSample: parseInt(bitDepthCode, 10),
1829
      cbSize: 0,
1830
      validBitsPerSample: 0,
1831
      dwChannelMask: 0,
1832
      subformat: []
1833
    };
1834
  }
1835
1836
  /**
1837
   * Create the header of a ADPCM wave file.
1838
   * @param {string} bitDepthCode The audio bit depth
1839
   * @param {number} numChannels The number of channels
1840
   * @param {number} sampleRate The sample rate.
1841
   * @param {number} numBytes The number of bytes each sample use.
1842
   * @param {number} samplesLength The length of the samples in bytes.
1843
   * @param {!Object} options The extra options, like container defintion.
1844
   * @private
1845
   */
1846
  createADPCMHeader_(
1847
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1848
    this.createPCMHeader_(
1849
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1850
    this.chunkSize = 40 + samplesLength;
1851
    this.fmt.chunkSize = 20;
1852
    this.fmt.byteRate = 4055;
1853
    this.fmt.blockAlign = 256;
1854
    this.fmt.bitsPerSample = 4;
1855
    this.fmt.cbSize = 2;
1856
    this.fmt.validBitsPerSample = 505;
1857
    this.fact = {
1858
      chunkId: 'fact',
1859
      chunkSize: 4,
1860
      dwSampleLength: samplesLength * 2
1861
    };
1862
  }
1863
1864
  /**
1865
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
1866
   * @param {string} bitDepthCode The audio bit depth
1867
   * @param {number} numChannels The number of channels
1868
   * @param {number} sampleRate The sample rate.
1869
   * @param {number} numBytes The number of bytes each sample use.
1870
   * @param {number} samplesLength The length of the samples in bytes.
1871
   * @param {!Object} options The extra options, like container defintion.
1872
   * @private
1873
   */
1874
  createExtensibleHeader_(
1875
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1876
    this.createPCMHeader_(
1877
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1878
    this.chunkSize = 36 + 24 + samplesLength;
1879
    this.fmt.chunkSize = 40;
1880
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
1881
    this.fmt.cbSize = 22;
1882
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
1883
    this.fmt.dwChannelMask = dwChannelMask(numChannels);
1884
    // subformat 128-bit GUID as 4 32-bit values
1885
    // only supports uncompressed integer PCM samples
1886
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
1887
  }
1888
1889
  /**
1890
   * Create the header of mu-Law and A-Law wave files.
1891
   * @param {string} bitDepthCode The audio bit depth
1892
   * @param {number} numChannels The number of channels
1893
   * @param {number} sampleRate The sample rate.
1894
   * @param {number} numBytes The number of bytes each sample use.
1895
   * @param {number} samplesLength The length of the samples in bytes.
1896
   * @param {!Object} options The extra options, like container defintion.
1897
   * @private
1898
   */
1899
  createALawMulawHeader_(
1900
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1901
    this.createPCMHeader_(
1902
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1903
    this.chunkSize = 40 + samplesLength;
1904
    this.fmt.chunkSize = 20;
1905
    this.fmt.cbSize = 2;
1906
    this.fmt.validBitsPerSample = 8;
1907
    this.fact = {
1908
      chunkId: 'fact',
1909
      chunkSize: 4,
1910
      dwSampleLength: samplesLength
1911
    };
1912
  }
1913
1914
  /**
1915
   * Validate the header of the file.
1916
   * @throws {Error} If any property of the object appears invalid.
1917
   * @private
1918
   */
1919
  validateWavHeader_() {
1920
    this.validateBitDepth_();
1921
    this.validateNumChannels_();
1922
    this.validateSampleRate_();
1923
  }
1924
1925
  /**
1926
   * Validate the bit depth.
1927
   * @return {boolean} True is the bit depth is valid.
1928
   * @throws {Error} If bit depth is invalid.
1929
   * @private
1930
   */
1931
  validateBitDepth_() {
1932
    if (!this.WAV_AUDIO_FORMATS[this.bitDepth]) {
1933
      if (parseInt(this.bitDepth, 10) > 8 &&
1934
          parseInt(this.bitDepth, 10) < 54) {
1935
        return true;
1936
      }
1937
      throw new Error('Invalid bit depth.');
1938
    }
1939
    return true;
1940
  }
1941
1942
  /**
1943
   * Validate the number of channels.
1944
   * @return {boolean} True is the number of channels is valid.
1945
   * @throws {Error} If the number of channels is invalid.
1946
   * @private
1947
   */
1948
  validateNumChannels_() {
1949
    /** @type {number} */
1950
    let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
1951
    if (this.fmt.numChannels < 1 || blockAlign > 65535) {
1952
      throw new Error('Invalid number of channels.');
1953
    }
1954
    return true;
1955
  }
1956
1957
  /**
1958
   * Validate the sample rate value.
1959
   * @return {boolean} True is the sample rate is valid.
1960
   * @throws {Error} If the sample rate is invalid.
1961
   * @private
1962
   */
1963
  validateSampleRate_() {
1964
    /** @type {number} */
1965
    let byteRate = this.fmt.numChannels *
1966
      (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
1967
    if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
1968
      throw new Error('Invalid sample rate.');
1969
    }
1970
    return true;
1971
  }
1972
}
1973